package com.ztr.conversion.util;

import cn.hutool.core.io.FileUtil;
import com.ztr.conversion.config.Constants;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import net.coobird.thumbnailator.Thumbnails;

import java.io.IOException;

/**
 * <p>
 * html 转 图片或pdf 工具类
 * </p>
 *
 * @description https://wkhtmltopdf.org
 * html转pdf： wkhtmltopdf https://zhengqing.blog.csdn.net zhengqingya.pdf
 * html转图片： wkhtmltoimage https://zhengqing.blog.csdn.net zhengqingya.png
 * 帮助 wkhtmltopdf -h 或 wkhtmltoimage -h
 */
@Slf4j
public class WkHtmlUtil {


    private static final String TOOL_WIN_ROOT_DIRECTORY = "D:/zhengqingya/soft/soft-dev/wkhtmltopdf/bin/";

    /**
     * 主函数，用于演示如何将网页转换为图片和PDF。
     *
     * @param args 命令行参数，本例中未使用。
     * @throws Exception 如果转换过程中发生错误，则抛出异常。
     */
    public static void main(String[] args) throws Exception {
        // 源网页的URL。
        String sourceFilePath = "https://zhengqing.blog.csdn.net";
        // 生成的图片文件路径。
        String targetPngFilePath = Constants.DEFAULT_FOLDER_TMP_GENERATE + "/zhengqingya.png";
        // 生成的PDF文件路径。
        String targetPdfFilePath = Constants.DEFAULT_FOLDER_TMP_GENERATE + "/zhengqingya.pdf";

        // 图片转换参数，指定裁剪宽度、高度和质量。
        // 设置宽高
        String cmdByImage = "--crop-w 150 --crop-h 150 --quality 100";

        // 调用方法将网页转换为图片字节流，此处使用了压缩版本的方法。
//        byte[] imageBytes = html2ImageBytes(cmdByImage, sourceFilePath, targetPngFilePath);
        byte[] imageBytesByCompress = html2ImageBytesByCompress(cmdByImage, sourceFilePath, targetPngFilePath);

        // 调用方法将网页转换为PDF字节流。
        byte[] pdfBytes = html2PdfBytes("", sourceFilePath, targetPdfFilePath);
    }


    /**
     * 使用wkhtmltoimage命令行工具将HTML文件转换为图像字节流。
     * <p>
     * 此方法封装了对wkhtmltoimage命令行工具的调用，将HTML文件转换为指定格式的图像。
     * 主要用于在不显示HTML内容的场景下，如邮件正文渲染，将HTML内容转换为图片形式发送。
     *
     * @param cmd            命令行参数，用于指定wkhtmltoimage工具的具体行为，如输出图像的格式和质量等。
     * @param sourceFilePath HTML文件的路径，wkhtmltoimage将从这个文件读取HTML内容。
     * @param targetFilePath 输出图像的路径，转换后的图像将保存在这个文件。
     * @return 返回转换后的图像的字节流，可以直接用于进一步处理或发送。
     */
    public static byte[] html2ImageBytes(String cmd, String sourceFilePath, String targetFilePath) {
        // 调用基础工具方法执行wkhtmltoimage命令，并返回图像的字节流。
        return baseTool("wkhtmltoimage", cmd, sourceFilePath, targetFilePath);
    }


    /**
     * 将HTML页面转换为图片字节序列。
     * 使用wkhtmltoimage工具将指定的HTML页面转换为图片，并返回该图片的字节序列。
     * 此方法封装了HTML到图片的转换过程，包括命令行构建、图片缩放和字节读取。
     *
     * @param cmd            wkhtmltoimage命令行参数，用于定制转换过程。
     * @param sourceFilePath HTML文件的路径，wkhtmltoimage将从这个文件读取HTML内容。
     * @param targetFilePath 生成的图片文件的目标路径。
     * @return 图片的字节序列，可以直接用于存储或传输。
     * @throws Exception 如果转换过程中发生错误，包括文件读写错误和转换工具的执行错误。
     */
    @SneakyThrows(Exception.class)
    public static byte[] html2ImageBytesByCompress(String cmd, String sourceFilePath, String targetFilePath) {
        // 构建wkhtmltoimage命令行工具的执行路径，为转换过程做准备。
        String filePath = baseToolForPath("wkhtmltoimage", cmd, sourceFilePath, targetFilePath);

        // 使用Thumbnails.of加载文件，并设置缩放比例为1（保持原尺寸），然后将转换后的图片保存到目标路径。
        Thumbnails.of(filePath)
                .scale(1f)
                .toFile(targetFilePath);

        // 读取目标文件的字节序列，准备返回给调用者。
        return FileUtil.readBytes(targetFilePath);
    }


    /**
     * 将HTML字符串转换为PDF字节流。
     * 该方法通过调用wkhtmltopdf工具将指定的HTML文件转换为PDF格式，返回转换后的PDF文件的字节流。
     * 使用此方法需要确保系统中已安装wkhtmltopdf工具，并且可从命令行访问。
     *
     * @param cmd            额外的命令行参数，用于定制HTML到PDF的转换过程。
     * @param sourceFilePath 源HTML文件的路径。
     * @param targetFilePath 目标PDF文件的路径。
     * @return 返回转换后的PDF文件的字节流。如果转换失败，可能返回null。
     */
    public static byte[] html2PdfBytes(String cmd, String sourceFilePath, String targetFilePath) {
        // 调用基础工具方法执行HTML到PDF的转换，并返回转换结果的字节流。
        return baseTool("wkhtmltopdf", cmd, sourceFilePath, targetFilePath);
    }


    /**
     * 使用指定工具处理文件，并返回处理后的文件内容。
     * <p>
     * 此方法封装了使用特定工具对文件进行处理的逻辑。它首先通过调用baseToolForPath方法确定处理后文件的路径，
     * 然后读取该文件的内容并以字节数组的形式返回。
     *
     * @param tool           工具的名称，用于指定执行文件处理的工具。
     * @param cmd            工具的命令参数，用于指定工具的具体操作。
     * @param sourceFilePath 源文件的路径，即待处理的文件路径。
     * @param targetFilePath 目标文件的路径，即处理后文件的保存路径。
     * @return 处理后文件的内容，以字节数组的形式表示。
     * @throws Exception 如果在读取文件内容时发生错误，则抛出异常。
     */
    @SneakyThrows({Exception.class})
    private static byte[] baseTool(String tool, String cmd, String sourceFilePath, String targetFilePath) {
        // 通过调用baseToolForPath方法确定处理后文件的路径
        String filePath = baseToolForPath(tool, cmd, sourceFilePath, targetFilePath);
        // 读取处理后文件的内容并返回
        return FileUtil.readBytes(filePath);
    }


    /**
     * 使用特定工具对文件进行处理的基础工具方法。
     * 该方法用于封装文件处理的通用逻辑，包括执行命令、等待命令完成等。
     *
     * @param tool           工具名称，用于构成命令的一部分。
     * @param cmd            命令的具体操作，用于指示工具进行何种操作。
     * @param sourceFilePath 源文件路径，即要处理的文件路径。
     * @param targetFilePath 目标文件路径，即处理后的文件保存路径。
     * @return 目标文件路径，用于链式调用或其他处理。
     * @throws Exception 如果执行命令失败或工具不存在，则抛出异常。
     */
    @SneakyThrows({Exception.class})
    private static String baseToolForPath(String tool, String cmd, String sourceFilePath, String targetFilePath) {
        // 创建目标文件的父目录，确保目标文件可以被成功创建。
        // 先创建父目录
        FileUtil.mkParentDirs(targetFilePath);

        // 构造命令行命令，包括工具路径、操作命令、源文件路径和目标文件路径。
        String command = String.format("%s %s %s %s", getToolRootPath() + tool, cmd, sourceFilePath, targetFilePath);

        Process process = null;
        try {
            // 执行命令行命令。
            process = Runtime.getRuntime().exec(command);
        } catch (IOException e) {
            // 如果执行命令失败，抛出异常，提示工具丢失。
            throw new Exception("工具丢失，请联系系统管理员！");
        }

        // 等待命令行命令执行完成。
        // 等待当前命令执行完，再往下执行
        process.waitFor();

        // 记录命令执行完成的日志。
        log.info("=============== FINISH: [{}] ===============", command);

        // 返回目标文件路径。
        return targetFilePath;
    }


    /**
     * 获取工具的根路径。
     * 根据操作系统的不同，返回相应的工具根目录路径。
     * 如果操作系统是Windows，则返回Windows下的工具根目录路径。
     * 如果操作系统是Linux或Mac OS X，则返回空字符串，表示在这些操作系统下不需要指定工具根目录。
     *
     * @return String 返回工具的根路径，可能是Windows下的路径或者空字符串。
     */
    private static String getToolRootPath() {
        // 获取当前操作系统的名称
        String system = System.getProperty("os.name");

        // 判断操作系统是否为Windows，如果是，则返回Windows工具根目录路径
        if (system.contains("Windows")) {
            return TOOL_WIN_ROOT_DIRECTORY;
        } else if (system.contains("Linux") || system.contains("Mac OS X")) {
            // 如果操作系统为Linux或Mac OS X，返回空字符串
            return "";
        }
        // 对于其他操作系统，也返回空字符串，目前未处理其他操作系统的情况
        return "";
    }


}
